﻿using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Markup;
using System.Windows.Controls;

namespace XamlKru.Controls.Primitives
{
    public class ImageTriangle : Control
    {
        private const String _imageTriangleContentTemplate
            = "<ControlTemplate xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"" +
                "		          xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"" +
                "	              xmlns:xk=\"clr-namespace:XamlKru.Controls.Primitives;assembly=XamlKru.Controls\"" +
                "	              TargetType=\"xk:ImageTriangle\">" +
                "   <Path x:Name=\"PathElement\" " +
                "	      Width=\"100\" Height=\"100\"" +
                "	      Data=\"M 0,0 100,0 0,100 0,0\"" +
                "   >" +
                "   <Path.RenderTransform>" +
                "	    <TransformGroup>" +
                "		    <ScaleTransform x:Name=\"PathScale\" ScaleX=\"1.01\" CenterX=\"25\" CenterY=\"25\" />" +
                "		    <MatrixTransform x:Name=\"PathTransform\" />" +
                "	    </TransformGroup>" +
                "	</Path.RenderTransform>" +
                "	<Path.Fill>" +
                "	    <ImageBrush x:Name=\"PathBrush\" " +
                "			AlignmentX=\"Left\" AlignmentY=\"Top\" Stretch=\"Fill\"" +
                "	    >" +
                "		    <ImageBrush.RelativeTransform>" +
                "		        <MatrixTransform x:Name=\"BrushTransform\"  />" +
                "		    </ImageBrush.RelativeTransform>" +
                "	    </ImageBrush>" +
                "	</Path.Fill>" +
                "    </Path>" +
                "</ControlTemplate>";

        #region Members

        private Boolean _recalculate = true;

        private Double AntiSeamOverscale = 1d;

        #endregion

        #region TemplateParts

        private const String _pathScaleElementName = "PathScale";
        private ScaleTransform _pathScale;

        private const String _pathTransformElementName = "PathTransform";
        private MatrixTransform _pathTransform;

        private const String _brushTransformElementName = "BrushTransform";
        private MatrixTransform _brushTransform;

        private const String _imageBrushElementName = "PathBrush";
        private ImageBrush _imageBrush;

        #endregion

        #region Dependency Properties

        public static readonly DependencyProperty Point1Property;

        public static readonly DependencyProperty Point2Property;

        public static readonly DependencyProperty Point3Property;

        public static readonly DependencyProperty TexturePositionsProperty;

        public static readonly DependencyProperty TextureSourceProperty;

        #endregion

        #region Properties

        public Point Point1
        {
            get { return (Point)GetValue(Point1Property); }
            set { SetValue(Point1Property, value); }
        }

        public Point Point2
        {
            get { return (Point)GetValue(Point2Property); }
            set { SetValue(Point2Property, value); }
        }

        public Point Point3
        {
            get { return (Point)GetValue(Point3Property); }
            set { SetValue(Point3Property, value); }
        }

        public PointCollection TexturePositions
        {
            get { return (PointCollection)GetValue(TexturePositionsProperty); }
            set { SetValue(TexturePositionsProperty, value); }
        }

        public ImageSource TextureSource
        {
            get { return (ImageSource)GetValue(TextureSourceProperty); }
            set { SetValue(TextureSourceProperty, value); }
        }

        public Boolean IsClockwise
        {
            get
            {
                Point p1 = Point1, p2 = Point2, p3 = Point3;
                return (p1.X - p3.X) * (p2.Y - p3.Y) - (p1.Y - p3.Y) * (p2.X - p3.X) <= 0d;
            }
        }

        #endregion

        #region Constructors

        static ImageTriangle()
        {
            Point1Property = DependencyProperty.Register("Point1",
                                                         typeof(Point),
                                                         typeof(ImageTriangle),
                                                         new PropertyMetadata(OnPointChanged));

            Point2Property = DependencyProperty.Register("Point2",
                                                         typeof(Point),
                                                         typeof(ImageTriangle),
                                                         new PropertyMetadata(OnPointChanged));

            Point3Property = DependencyProperty.Register("Point3",
                                                         typeof(Point),
                                                         typeof(ImageTriangle),
                                                         new PropertyMetadata(OnPointChanged));

            TexturePositionsProperty = DependencyProperty.Register("TexturePositions",
                                                                   typeof(PointCollection),
                                                                   typeof(ImageTriangle),
                                                                   new PropertyMetadata(OnTexturePositionsChanged));


            TextureSourceProperty = DependencyProperty.Register("ImageSource",
                                                                typeof(ImageSource),
                                                                typeof(ImageTriangle),
                                                                new PropertyMetadata(OnImageSourceChanged));
        }

        public ImageTriangle()
        {
            Template = (ControlTemplate)XamlReader.Load(_imageTriangleContentTemplate);
            ApplyTemplate();

            Point1 = new Point(0, 0);
            Point2 = new Point(100, 0);
            Point3 = new Point(0, 100);

            UpdateTexturePositions(null);
        }

        #endregion

        #region Public methods

        public void SetPoints(Point p1, Point p2, Point p3)
        {
            _recalculate = false;

            Point1 = p1; Point2 = p2; Point3 = p3;

            UpdateCorners(p1, p2, p3);

            _recalculate = true;
        }

        #endregion

        #region Control overrides

        private Boolean _templateAlreadyApplied = false;

        public override void OnApplyTemplate()        
        {            
            if (_templateAlreadyApplied)
            {
                throw new InvalidOperationException("The template can only be applied one time.");
            }

            _templateAlreadyApplied = true;

            _pathScale = (ScaleTransform)GetTemplateChild(_pathScaleElementName);
            _pathTransform = (MatrixTransform)GetTemplateChild(_pathTransformElementName);

            _imageBrush = (ImageBrush)GetTemplateChild(_imageBrushElementName);

            _brushTransform = (MatrixTransform)GetTemplateChild(_brushTransformElementName);
        }

        #endregion

        #region Implementation

        private static void OnPointChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ImageTriangle triangle = (ImageTriangle)obj;

            Point p1 = triangle.Point1;
            Point p2 = triangle.Point2;
            Point p3 = triangle.Point3;

            if (triangle._recalculate)
            {
                triangle.UpdateCorners(p1, p2, p3);
            }
        }

        private static void OnTexturePositionsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ImageTriangle triangle = (ImageTriangle)obj;

            PointCollection oldCorners = (PointCollection)args.OldValue;
            PointCollection newCorners = (PointCollection)args.NewValue;

            triangle.UpdateTexturePositions(newCorners);
        }

        private static void OnImageSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
        {
            ImageTriangle triangle = (ImageTriangle)obj;
            ImageSource imgSrc = (ImageSource)args.NewValue;

            triangle._imageBrush.ImageSource = imgSrc;
        }

        private void UpdateCorners(Point p1, Point p2, Point p3)
        {
            // set transformation matrix
            Double m11 = (p2.X - p1.X) * 0.01d;
            Double m12 = (p2.Y - p1.Y) * 0.01d;
            Double m21 = (p3.X - p1.X) * 0.01d;
            Double m22 = (p3.Y - p1.Y) * 0.01d;
            Double ox = p1.X;
            Double oy = p1.Y;

            _pathTransform.Matrix = new Matrix(m11, m12, m21, m22, ox, oy);

            // scale a little bit, so the borders overlap just enough
            // to remove the seam between triangles
            
            // find bounding rectangle
            var rect = new Rect(p1, p2);
            rect.Union(p3);

            Double w = rect.Width, h = rect.Height;

            _pathScale.ScaleX = w > 0 ? (w + AntiSeamOverscale) / w : 0;
            _pathScale.ScaleY = h > 0 ? (h + AntiSeamOverscale) / h : 0;
        }

        private void UpdateTexturePositions(PointCollection positions)
        {
            Point p1, p2, p3;

            if (positions == null)
            {
                p1 = new Point(0, 0);
                p2 = new Point(1, 0);
                p3 = new Point(0, 1);
            }
            else
            {
                if (positions.Count != 3)
                    throw new ArgumentOutOfRangeException("positions");

                p1 = positions[0];
                p2 = positions[1];
                p3 = positions[2];
            }

            Double m11 = (p2.X - p1.X);
            Double m12 = (p2.Y - p1.Y);
            Double m21 = (p3.X - p1.X);
            Double m22 = (p3.Y - p1.Y);
            Double ox = p1.X;
            Double oy = p1.Y;

            var m = new Matrix(m11, m12, m21, m22, ox, oy)
                    .Invert();

            _brushTransform.Matrix = m;
        }

        #endregion
    }
}

